-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add smarter type inference for ofType pipe. #1183
Conversation
35b3ab4
to
d04cae3
Compare
modules/effects/src/actions.ts
Outdated
import { filter } from 'rxjs/operators'; | ||
|
||
// Removes types from T that are not assignable to U. | ||
export type Filter<T, U> = T extends U ? T : never; | ||
|
||
@Injectable() | ||
export class Actions<V = Action> extends Observable<V> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if I should also remove lift and ctor impl as proposed in #860?
@@ -0,0 +1,12 @@ | |||
export default { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I copied those from testing
but I have not idea whether they are correct in the context of compat.
Should we allow an "infite" number of |
That would be ideal, but the type system in TS 2.9 isn't powerful enough to check that many. 3.0 (in RC now) improves on this but I'm not sure if it'll be enough. It'll be interesting to check out when it lands. |
@mtaran-google I see, thanks. Just one more thing, I thought the following would give me compile errors but they don't: // this is OK
ofType<Search>(BookActionTypes.Search),
// I would expect an error here
ofType<Search>('sss'),
// I would expect an error here, because the payload of `SearchComplete` is an array
ofType<Search>(BookActionTypes.SearchComplete),
map(action => action.payload),
switchMap(query => {
if (query === '') {
return empty();
} I fiddled with it in the past and came up with this which is giving compile errors. |
@timdeschryver That's a good point. Let's recap the desired outcomes:
My current PR has 2) but not 3), and yours I think has 3) but not 2), because you have two free type variables I think 3) is not as bad as it seems. If we have property 1) eventually, code will be written without any explicit types in <>, thus irrelevant how is the argument vs explicit type annotation resolved. Until then one just has to use the rule-of-thumb "if <> explicitly specified, it wins", i.e. think of it as a type assertion. |
However, based on your your example it looks like the T1,T2,..., etc expansions are not necessary. I think the following works the same. export function ofType<
V extends Extract<U, { type: T }>,
T extends string = string,
U extends Action = Action
>(...t: T[]): OperatorFunction<U, V>; Also one final thing, my work here is prompted by attempting to turn on --strictFunctionTypes and hitting a lot of ngrx issues. Please do your testing with that flag on. I think any solution that doesn't have at least two parameters U -> V to capture the broader observable input and the narrowed out observable output, will not work with --strictFunctionTypes. |
|
@alex-okrushko That is the blocker for all this. TS2.8 is required for conditional types, Maksym's original change a custom alias for |
@rkirov Yep you're completely right. And the snippet you posted has the same outcome. export function ofType<
V extends Extract<U, { type: T }>,
T extends string = string,
U extends Action = Action
>(...t: T[]): OperatorFunction<U, V>; |
Ok, I just discovered one more issue that might be the deal breaker here. A lot of teams do not pass they type argument on their actions observable is it just of type - However, for unqualifed This is expected because |
I updated this PR with what is my final proposal for the future types of 'ofType'. Added comments that should capture the upside and downsides of that approach. Hopefully, that would make the type soup a bit more maintainable. Internally, I appears we have an agreement that this is good and are going to change it with a patch before this PR lands here. Here it appears to be stuck behind angular, bazel, typescript versioning hell, that I can't resolve. @MikeRyanDev and @brandonroberts please at least verify that there are no major concerns with this proposal, otherwise we would end up forking out internal google users from the mainstream branch here. |
@rkirov This proposed change looks good to me. |
@rkirov I agree also |
modules/effects/src/actions.ts
Outdated
import { filter } from 'rxjs/operators'; | ||
|
||
// Removes types from T that are not assignable to U. | ||
export type Filter<T, U> = T extends U ? T : never; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we're using Extract
this can be removed now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed.
@@ -19,17 +22,79 @@ export class Actions<V = Action> extends Observable<V> { | |||
observable.operator = operator; | |||
return observable; | |||
} | |||
|
|||
ofType<V2 extends V = V>(...allowedTypes: string[]): Actions<V2> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may sound stupid, but here it goes anyways:
Since we have the fallback ofType
, could we not remove this but mark it as deprecated?
With the next release we can remove it.
I say this because while the compat
solution works, the user would have to change the import statement?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have no opinions on backwards compat (google operates on one-version policy), and will do whatever you guys decide.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was planning on doing a 6.1 release, so maybe we can deprecate the instance operators before release and then move forward with the removal for the next major release.
package.json
Outdated
@@ -143,7 +143,7 @@ | |||
"tslib": "1.6.0", | |||
"tslint": "^5.0.0", | |||
"tsutils": "2.20.0", | |||
"typescript": "~2.7.2", | |||
"typescript": "~2.8.4", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this means that angular version would need a bump as well:
@angular/compiler-cli@6.0.9 requires typescript@'>=2.7.0 <2.8.0' but 2.8.4 was found instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, at this point this PR is doing too much. Another PR is needed to get to TS 2.8, with all the complications involved around angular, bazel, router(?). I can't do that upgrade, but will rebase this PR once that happens.
Opened #1220 to track the compiler upgrade. This PR is blocked (will respond to review comments though) until that gets resolved. |
rebased the PR, will take a look at the CI results tomorrow. |
CI is green, please proceed with a regular review. I don't fully understand the compat strategy, mostly copy/pasted from other directories. |
I think the |
@rkirov @timdeschryver I think its safe to remove the |
modules/effects/spec/actions.spec.ts
Outdated
@@ -11,12 +11,20 @@ import { hot, cold } from 'jasmine-marbles'; | |||
import { of } from 'rxjs'; | |||
|
|||
describe('Actions', function() { | |||
let actions$: Actions; | |||
let actions$: Actions<AddAcction | SubtractAction>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AddAcction -> AddAction
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@rkirov Hey Rado. Would you be able to take a look at this PR again? |
Any update on this? I can finalize this PR, if you don't mind. For someone who wants to try this feature in your project today, you can add this to your codebase: declare module '@ngrx/effects' {
export function ofType<
V extends Extract<U, { type: T }>,
T extends string = string,
U extends Action = Action
>(...actions: T[]): OperatorFunction<U, V>;
} |
If you want I can finish this one up @rkirov but you'll have to give maintainers write access to your branch. |
I will rebase it today. Sorry for the delay. Btw, we have been running this with change internally in google for some time and I haven't heard any complaints. |
It is based on TS2.8 introduced conditional types. Upgrade package.json to that version. Also remove the deprecated non-pipable version of `ofType` so that we don't have to keep complicated types in two places. Fixes some tests post removal of non-pipable version. Original implementation by @mtaran-google. BREAKING CHANGES: Removes .ofType method on Actions. Instead use the provided 'ofType' rxjs operator. BEFORE: ``` this.actions.ofType('INCREMENT') ``` AFTER: ``` import { ofType } from '@ngrx/store'; ... this.action.pipe(ofType('INCREMENT')) ```
Rebased, removed compat bits and updated commit message with 'BREAKING CHANGE'. Please take another look. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Thanks @rkirov! |
It is based on TS2.8 introduced conditional types. Upgrade package.json
to that version.
Also remove the deprecated non-pipable version of
ofType
so that wedon't have to keep complicated types in two places.
Fixes some tests post removal of non-pipable version.
Original implementation by @mtaran-google.
Fixes #860